package com.github.data_manage.service

import com.github.data_manage.config.FUND_NO_DATA_EXCEPTION
import com.github.data_manage.config.SystemException
import com.github.data_manage.dao.FundBusinessDataDao
import com.github.data_manage.dao.FundDao
import com.github.data_manage.dao.FundNavDataDao
import com.github.data_manage.dao.FundReturnDataDao
import com.github.data_manage.domain.dto.QuarterAnnualizedResult
import com.github.data_manage.domain.dto.QuarterAnnualizedResults
import com.github.data_manage.domain.dto.YearAnnualizedResult
import com.github.data_manage.domain.dto.YearAnnualizedResults
import com.github.data_manage.domain.dto.YearQuarterAnnualizedResult
import com.github.data_manage.domain.po.*
import com.github.data_manage.domain.vo.AnalysisVo
import com.github.data_manage.util.DataUtils
import com.github.data_manage.util.DataUtils.standardDeviation
import com.github.data_manage.util.DataUtils.nav
import com.github.data_manage.util.DataUtils.ytd
import com.github.data_manage.util.DateTimeUtils.Companion.atEndOfMonth
import com.github.data_manage.util.DateTimeUtils.Companion.quarter
import org.slf4j.LoggerFactory
import org.springframework.data.repository.findByIdOrNull
import org.springframework.stereotype.Service
import org.springframework.transaction.annotation.Transactional
import org.springframework.util.StringUtils
import java.math.BigDecimal
import java.math.RoundingMode
import java.time.LocalDate
import java.util.TreeMap

@Service
class FundService(
    private val fundBusinessDataDao: FundBusinessDataDao,
    private val fundNavDataDao: FundNavDataDao,
    private val fundReturnDataDao: FundReturnDataDao,
    private val fundDao: FundDao,
) {

    companion object {
        private val log = LoggerFactory.getLogger(FundService::class.java)

        private val aumCodeMap: Map<String, Int> = mapOf(
            Pair("股票多空（Equity L/S）", 10),
            Pair("股票多空（Equity L/S）-Long Biased", 11),
            Pair("股票多空（Equity L/S）-Low Net", 12),
            Pair("股票多空（Equity L/S）-Variable Net", 13),
            Pair("股票多空（Equity L/S）-Market Neutral", 14),
            Pair("债券策略（Credit）", 20),
            Pair("债券策略（Credit）-Distressed", 21),
            Pair("债券策略（Credit）-Convertible Bond", 22),
            Pair("债券策略（Credit）-High Yield", 23),
            Pair("债券策略（Credit）-Structured Credit", 24),
            Pair("宏观策略（Macro）", 30),
            Pair("宏观策略（Macro）-Directional", 31),
            Pair("宏观策略（Macro）-Systematic", 32),
            Pair("宏观策略（Macro）-FX", 33),
            Pair("宏观策略（Macro）-Rates", 34),
            Pair("宏观策略（Macro）-Commodities", 35),
            Pair("宏观策略（Macro）-Financial Instruments", 36),
            Pair("量化策略（Quantitative）", 40),
            Pair("量化策略（Quantitative）-High Frequency", 41),
            Pair("量化策略（Quantitative）-Mid/Low Frequency", 42),
            Pair("量化策略（Quantitative）-Multi-factor", 43),
            Pair("量化策略（Quantitative）-Machine Learning", 44),
            Pair("套利策略（Arbitrage）", 50),
            Pair("套利策略（Arbitrage）-    Equity Arb", 51),
            Pair("套利策略（Arbitrage）-Fixed Income Arb", 52),
            Pair("套利策略（Arbitrage）-Macro Arb", 53),
            Pair("套利策略（Arbitrage）-Quant Arb", 54),
            Pair("事件驱动（Event Driven）", 60),
            Pair("事件驱动（Event Driven）-M&A", 61),
            Pair("事件驱动（Event Driven）-Activism", 62),
            Pair("事件驱动（Event Driven）-ECM", 63),
            Pair("事件驱动（Event Driven）-Other Event", 64),
        )
    }

    fun fundDataTimeRange(fundId: Int): Pair<LocalDate, LocalDate> {
        val fundValueMinDate = fundBusinessDataDao.findMinTimeByFundId(fundId)
        val fundValueMaxDate = fundBusinessDataDao.findMaxTimeByFundId(fundId)
        if (fundValueMaxDate == null || fundValueMinDate == null) {
            throw SystemException("基金无数据", FUND_NO_DATA_EXCEPTION)
        }
        return Pair(fundValueMinDate, fundValueMaxDate)
    }

    fun calcFundNavData(fundId: Int) {
        fundNavDataDao.deleteAllByFundId(fundId)
        val fundAllBusinessData = fundBusinessDataDao.findAllByFundId(fundId)
        if (fundAllBusinessData.isEmpty()) {
            return
        }
        // 起始值为最小数据月 - 1,设置为100
        val firstFundBusinessData = fundAllBusinessData.first()

        val startNavData = fundNavDataDao.findFundNavDataByFundIdAndTime(
            fundId,
            firstFundBusinessData.time!!.minusMonths(1).atEndOfMonth()
        ) ?: FundNavData()
        startNavData.fundId = fundId
        startNavData.fundName = firstFundBusinessData.fundName
        startNavData.time = firstFundBusinessData.time!!.minusMonths(1).atEndOfMonth()
        startNavData.value = BigDecimal.valueOf(100)
        fundNavDataDao.save(startNavData)

        for (fundBusinessData in fundAllBusinessData) {

            // 计算NAV
            val lastNavData =
                fundNavDataDao.findLastNavDataByFundId(fundId, fundBusinessData.time!!.minusMonths(1).atEndOfMonth())!!
            val fundNavData =
                fundNavDataDao.findFundNavDataByFundIdAndTime(fundId, fundBusinessData.time!!) ?: FundNavData()

            fundNavData.fundId = fundId
            fundNavData.fundName = fundBusinessData.fundName
            fundNavData.time = fundBusinessData.time
            fundNavData.value = lastNavData.value * (BigDecimal.ONE + fundBusinessData.value.divide(
                BigDecimal.valueOf(100).setScale(2, RoundingMode.UP)
            )).setScale(
                4,
                RoundingMode.HALF_UP
            )
            fundNavDataDao.save(fundNavData)
        }
    }

    /**
     * 计算指定时间段内的nav
     */
    fun calcFundNavData(
        fund: Fund,
        fundAllBusinessData: List<FundBusinessData>,
    ): List<FundNavData> {
        if (fundAllBusinessData.isEmpty()) {
            return emptyList()
        }
        val sortedBusinessData = ArrayList<FundBusinessData>(fundAllBusinessData).sortedBy { it.time!! }
        val fundNavDataMap = TreeMap<LocalDate, FundNavData>()
        val startNavData = FundNavData()
        startNavData.fundId = fund.fundId!!
        startNavData.fundName = fund.fundName!!
        startNavData.time = sortedBusinessData.first().time!!.minusMonths(1).atEndOfMonth()
        startNavData.value = BigDecimal.valueOf(100)
        fundNavDataMap[startNavData.time!!] = startNavData

        for (contrastBusinessData in sortedBusinessData) {

            // 计算NAV
            val fundNavData = FundNavData()

            // 计算NAV
            val lastNavData = fundNavDataMap[contrastBusinessData.time!!.minusMonths(1).atEndOfMonth()]
                ?: fundNavDataMap.lastEntry().value

            fundNavData.fundId = fund.fundId!!
            fundNavData.fundName = fund.fundName!!
            fundNavData.time = contrastBusinessData.time
            fundNavData.value = (lastNavData.value * (BigDecimal.ONE + contrastBusinessData.value.divide(
                BigDecimal.valueOf(100).setScale(2, RoundingMode.UP)
            ))).setScale(
                2,
                RoundingMode.HALF_UP
            )

            fundNavDataMap[fundNavData.time!!] = fundNavData
        }
        return fundNavDataMap.values.toList().sortedByDescending { it.time }
    }


    fun calcFundReturnData(fundId: Int) {
        fundReturnDataDao.deleteAllByFundId(fundId)
        for (fundBusinessData in fundBusinessDataDao.findAllByFundId(fundId)) {
            val allFundNavData =
                fundNavDataDao.findAllByFundIdAndTime(fundId, fundBusinessData.time!!.atEndOfMonth())
            if (allFundNavData.isEmpty()) {
                log.error("数据内容不正确")
                return
            }
            val currentFundNavData = allFundNavData.last()
            val maxFundNavValue = allFundNavData.maxOfOrNull { it.value } ?: BigDecimal.valueOf(100)
            val fundReturnData =
                fundReturnDataDao.findByFundIdAndTime(fundId, fundBusinessData.time!!.atEndOfMonth())
                    ?: FundReturnData()
            fundReturnData.fundId = fundId
            fundReturnData.fundName = fundBusinessData.fundName
            fundReturnData.time = fundBusinessData.time
            fundReturnData.value =
                ((currentFundNavData.value.divide(maxFundNavValue, 4, RoundingMode.HALF_UP)) - BigDecimal.ONE).multiply(
                    BigDecimal.valueOf(100)
                )
                    .setScale(
                        4, RoundingMode.HALF_UP
                    )
            fundReturnDataDao.save(fundReturnData)
        }
    }

    fun calcFundReturnData(
        fund: Fund,
        fundAllBusinessData: List<FundBusinessData>
    ): List<FundReturnData> {
        val fundAllNavData = calcFundNavData(fund, fundAllBusinessData)
        return calcFundReturnData(fund, fundAllBusinessData, fundAllNavData)
    }

    fun calcFundReturnData(
        fund: Fund,
        fundAllBusinessData: List<FundBusinessData>,
        fundAllNavData: List<FundNavData>,
    ): List<FundReturnData> {
        return ArrayList<FundBusinessData>(fundAllBusinessData).sortedBy { it.time!! }.map { fundBusinessData ->
            val allFundNavData =
                fundAllNavData.filter { it.time!! <= fundBusinessData.time!! }.sortedBy { it.time }
            if (allFundNavData.isEmpty()) {
                return emptyList()
            }
            val currentFundNavData = allFundNavData.last()
            val maxFundNavValue = allFundNavData.maxOfOrNull { it.value } ?: BigDecimal.valueOf(100)
            val fundReturnData = FundReturnData()
            fundReturnData.fundId = fund.fundId
            fundReturnData.fundName = fundBusinessData.fundName
            fundReturnData.time = fundBusinessData.time
            fundReturnData.value =
                ((currentFundNavData.value.divide(
                    maxFundNavValue,
                    4,
                    RoundingMode.HALF_UP
                )) - BigDecimal.ONE).multiply(
                    BigDecimal.valueOf(100)
                )
                    .setScale(
                        4, RoundingMode.HALF_UP
                    )
            fundReturnData
        }
    }

    fun fundAnalysis(
        fundId: Int,
        startDate: LocalDate,
        endDate: LocalDate,
        pearsonCorrelationReturnData: List<BigDecimal> = emptyList()
    ): AnalysisVo {
        val fund = fundDao.findById(fundId).orElse(null) ?: throw SystemException("基金不存在")
        val allBusinessData =
            fundBusinessDataDao.findAllByFundIdAndTimeBetween(fundId, startDate, endDate)
                .sortedByDescending { it.time!! }
        val allFundNavData = calcFundNavData(fund, allBusinessData)
        val allFundReturnData =
            calcFundReturnData(fund, allBusinessData, allFundNavData)
        if (allBusinessData.isEmpty() || allFundNavData.isEmpty() || allFundReturnData.isEmpty()) {
            throw SystemException("基金无数据", FUND_NO_DATA_EXCEPTION)
        }
        val analysisVo = AnalysisVo()
        analysisVo.name = fund.fundName!!
        val currentYear = endDate.year - 1
        analysisVo.currentYear = currentYear

        val currentFundNav = allFundNavData.first()
        val currentFundNavValue = currentFundNav.value.setScale(4, RoundingMode.HALF_UP)
        analysisVo.currentMonthNav = currentFundNavValue
        analysisVo.currentMonthRequite = allBusinessData.first().value.setScale(4, RoundingMode.HALF_UP)
//            allBusinessData.filter { it.time!!.year == currentYear }.maxByOrNull { it.time!! }?.value?.setScale(
//                4,
//                RoundingMode.HALF_UP
//            )
        analysisVo.currentYearNav = allBusinessData.filter { it.time!!.year == currentYear }
            .map { it.value.divide(BigDecimal.valueOf(100), 4, RoundingMode.HALF_UP) }.ytd()


        val lastYear = currentYear - 1
        analysisVo.lastYear = lastYear
        analysisVo.lastYearNav = allBusinessData.filter { it.time!!.year == lastYear }
            .map { it.value.divide(BigDecimal.valueOf(100), 4, RoundingMode.HALF_UP) }.ytd()

        val beforeLastYear = currentYear - 2

        analysisVo.beforeLastYear = beforeLastYear
        analysisVo.beforeLastYearNav = allBusinessData.filter { it.time!!.year == beforeLastYear }
            .map { it.value.divide(BigDecimal.valueOf(100), 4, RoundingMode.HALF_UP) }.ytd()
        analysisVo.yearNav = fund.yearNav!!
        analysisVo.yearStandardDeviation = fund.yearStandardDeviation!!
        analysisVo.maxMonthNav = allBusinessData.maxOfOrNull { it.value } ?: BigDecimal.ZERO
        analysisVo.minMonthNav = allBusinessData.minOfOrNull { it.value } ?: BigDecimal.ZERO
        analysisVo.positiveNavPercent =
            BigDecimal.valueOf(allBusinessData.count { it.value.compareTo(BigDecimal.ZERO) > 0 }
                .toDouble() / allBusinessData.size).setScale(4, RoundingMode.DOWN)
                .multiply(BigDecimal.valueOf(100))
        analysisVo.sharpRatio = if (analysisVo.yearStandardDeviation.compareTo(BigDecimal.ZERO) == 0) {
            BigDecimal.ZERO
        } else {
            (analysisVo.yearNav.subtract(BigDecimal.valueOf(4))).divide(
                analysisVo.yearStandardDeviation,
                4,
                RoundingMode.HALF_UP
            )
        }

        analysisVo.maxReturn = allFundReturnData.minOfOrNull { it.value } ?: BigDecimal.ZERO
        analysisVo.type = 1
        analysisVo.correlation = if (pearsonCorrelationReturnData.isEmpty()) {
            null
        } else {
            DataUtils.pearsonCorrelation(pearsonCorrelationReturnData, allFundReturnData.map { it.value })
        }
        analysisVo.allReturnData = allFundReturnData.map { it.value }
        return analysisVo
    }

    fun fundYearInfo(fundId: Int): Pair<BigDecimal, BigDecimal> {
        val allBusinessData = fundBusinessDataDao.findAllByFundId(fundId).sortedByDescending { it.time!! }
        if (allBusinessData.isEmpty()) {
            return Pair(BigDecimal.ZERO, BigDecimal.ZERO)
        }
        val yearNav =
            allBusinessData.map { it.value.divide(BigDecimal.valueOf(100), 4, RoundingMode.HALF_UP) }.nav()
        val yearStandardDeviation = if (allBusinessData.isEmpty()) {
            BigDecimal.ZERO
        } else {
            allBusinessData.map { it.value.divide(BigDecimal.valueOf(100), 4, RoundingMode.HALF_UP) }
                .standardDeviation()
        }
        return Pair(yearNav, yearStandardDeviation)
    }

    /**
     * 基金年化业绩
     *
     */
    fun fundYearAnnualizedResults(
        fundId: Int,
        startDate: LocalDate,
        endDate: LocalDate,
    ): YearAnnualizedResults {
        val fund = fundDao.findByIdOrNull(fundId) ?: throw SystemException("指定id的基金未找到")
        val fundBusinessData = fundBusinessDataDao.findAllByFundIdAndTimeBetween(fundId, startDate, endDate)
        val yearInfos = fundBusinessData.groupBy { it.time!!.year }
            .entries.map { entry ->
                val (year, yearBusinessData) = entry
                // 总复利收益=(1+r1)×(1+r2)×⋯×(1+rn),ri为每个月的收益
                val totalValue = yearBusinessData.map {
                    it.value.divide(BigDecimal.valueOf(100), RoundingMode.HALF_UP)
                        .add(BigDecimal.ONE)
                }.reduce { a, b -> a * b }
                // R年化 =(总复利收益) 的 12/n 次方 −1
                val yearValue = totalValue.pow(12 / yearBusinessData.size).subtract(BigDecimal.ONE)
                    .setScale(2, RoundingMode.HALF_UP)
                YearAnnualizedResult(year, yearValue)
            }
        return YearAnnualizedResults(fundId, fund.fundName!!, 1, yearInfos)
    }

    /**
     * 基金年度业绩
     */
    fun fundYearAnnualResults(
        fundId: Int,
        startDate: LocalDate,
        endDate: LocalDate,
    ): YearAnnualizedResults {
        val fund = fundDao.findByIdOrNull(fundId) ?: throw SystemException("指定id的基金未找到")
        val fundBusinessData = fundBusinessDataDao.findAllByFundIdAndTimeBetween(fundId, startDate, endDate)
        val yearInfos = fundBusinessData.groupBy { it.time!!.year }
            .entries.map { entry ->
                val (year, yearBusinessData) = entry
                // 总复利收益=(1+r1)×(1+r2)×⋯×(1+rn),ri为每个月的收益
                val totalValue = yearBusinessData.map {
                    it.value.divide(BigDecimal.valueOf(100), RoundingMode.HALF_UP).add(BigDecimal.ONE)
                }
                    .reduce { a, b -> a * b }
                // R年化 =(总复利收益) 的 12/n 次方 −1
                val yearValue = totalValue.subtract(BigDecimal.ONE)
                    .setScale(2, RoundingMode.HALF_UP)
                YearAnnualizedResult(year, yearValue)
            }
        return YearAnnualizedResults(fundId, fund.fundName!!, 1, yearInfos)
    }

    /**
     * 基金季度化业绩
     */
    fun fundQuarterAnnualizedResults(
        fundId: Int,
        startDate: LocalDate,
        endDate: LocalDate,
    ): QuarterAnnualizedResults {
        val fund = fundDao.findByIdOrNull(fundId) ?: throw SystemException("指定id的基金未找到")
        val fundBusinessData = fundBusinessDataDao.findAllByFundIdAndTimeBetween(fundId, startDate, endDate)
        val yearInfos = fundBusinessData.groupBy { it.time!!.year }
            .entries.map { entry ->
                val (year, yearBusinessData) = entry
                var index = 0

                val quarters = yearBusinessData.groupBy { it.time!!.quarter() }
                    .map { entry ->
                        val quarterBusinessData = entry.value
                        index++
                        if (quarterBusinessData.size < 3) {
                            return@map QuarterAnnualizedResult(index, null)
                        }
                        // 总复利收益=(1+r1)×(1+r2)×⋯×(1+rn),ri为每个月的收益
                        val totalValue = quarterBusinessData.map {
                            it.value.divide(BigDecimal.valueOf(100), RoundingMode.HALF_UP).add(BigDecimal.ONE)
                        }
                            .reduce { a, b -> a * b }
                        // R年化 =(总复利收益) 的 12/n 次方 −1
                        val quarterValue = totalValue.subtract(BigDecimal.ONE)
                            .setScale(2, RoundingMode.HALF_UP)
                        return@map QuarterAnnualizedResult(index, quarterValue)
                    }
                YearQuarterAnnualizedResult(year, quarters)
            }
        return QuarterAnnualizedResults(fundId, fund.fundName!!, 1, yearInfos)
    }

    @Transactional
    fun buildAllFundCode() {
        fundDao.findAll().sortedBy { it.fundId!! }.forEach { fund ->
            try {
                val fundCode = buildFundCode(fund)
                if (fund.fundCode != fundCode) {
                    fundDao.updateFundCode(fundCode, fund.fundId!!)
                }
            } catch (e: Exception) {
                log.error("基金[{}],创建编号发生异常", fund.fundId!!, e)
            }
        }
    }

    @Suppress("all")
    fun buildFundCode(fund: Fund): String {
        val parentAumCode = fund.parentAum?.takeIf { StringUtils.hasText(it) }?.split(",")?.let { parentAums ->
            if (parentAums.size > 1) {
                return@let "99"
            }
            if (parentAums[0] == "Other") {
                return@let "70"
            }
            aumCodeMap[parentAums[0]]!!
        } ?: "00"
        val childAumCode = fund.childAum?.takeIf { StringUtils.hasText(it) }?.split(",")?.let { childAums ->
            if (childAums.size > 1) {
                return@let "99"
            }
            if (childAums[0] == "Other") {
                return@let "71"
            }
            aumCodeMap[childAums[0]]!!
        } ?: "00"
        val newCodePrefix = "$parentAumCode$childAumCode"
        val oldCodePrefix =
            fund.fundCode.takeIf { StringUtils.hasText(it) && it!!.length == 7 }?.substring(0, 4) ?: ""
        // 策略没有变,编号就不变
        if (newCodePrefix == oldCodePrefix) {
            return fund.fundCode!!
        }
        // 策略变了,那就是新的策略的序号最大值加1
        val fundSerialNo = fundDao.findMaxFundCode(newCodePrefix)?.let { maxFundCode: String ->
            val maxSerialNo = maxFundCode.substring(4).toInt()
            return@let String.format("%03d", maxSerialNo + 1)
        } ?: "001"
        return "$newCodePrefix$fundSerialNo"
    }

    @Transactional
    fun deleteFundCode(fund: Fund) {
        fundDao.findBiggerFundCodes(fund.fundCode!!.substring(0, 4), fund.fundCode!!).forEach { biggerFundCode ->
            fundDao.updateFundCode(biggerFundCode, (biggerFundCode.toInt() - 1).toString())
        }
    }
}
